package com.xbongbong.paas.script.help;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.FlowStatusEnum;
import com.xbongbong.paas.enums.FundAccountFlowTypeEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.script.pojo.FundAccountExplainsDTO;
import com.xbongbong.paas.script.pojo.dto.InitCustomerStatementDTO;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.JsonHelperUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.util.ExplainUtil;
import com.xbongbong.pro.constant.SymbolConstant;
import com.xbongbong.pro.enums.DistributorMarkEnum;
import com.xbongbong.pro.enums.SaasMarkEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.paymentsheet.pojo.dto.PaymentSheetUpdateBatchDTO;
import com.xbongbong.pro.paymentsheet.pojo.dto.PaymentSheetUpdateDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.ParameterConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.CustomerStatementEntity;
import com.xbongbong.saas.domain.entity.FundAccountFlowEntity;
import com.xbongbong.saas.domain.entity.ext.PayPlanSheetEntityExt;
import com.xbongbong.saas.domain.entity.ext.PaymentEntityExt;
import com.xbongbong.saas.domain.entity.ext.PaymentSheetEntityExt;
import com.xbongbong.saas.enums.CustomerStatementEnum;
import com.xbongbong.saas.enums.SheetAloneEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.PaySheetEnum;
import com.xbongbong.saas.enums.business.PaymentEnum;
import com.xbongbong.saas.enums.business.PaymentSheetEnum;
import com.xbongbong.saas.help.FundHelp;
import com.xbongbong.saas.help.SaasCustomerStatementHelper;
import com.xbongbong.saas.help.SaasUpdateHelp;
import com.xbongbong.saas.model.CustomerStatementModel;
import com.xbongbong.saas.model.FundAccountFlowModel;
import com.xbongbong.saas.model.PayPlanSheetModel;
import com.xbongbong.saas.model.PaymentModel;
import com.xbongbong.saas.model.PaymentSheetModel;
import com.xbongbong.saas.service.CustomerStatementService;
import com.xbongbong.saas.service.PaymentSheetService;
import com.xbongbong.saas.toolbox.help.FastJsonHelper;
import com.xbongbong.sys.model.CompanyModel;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zcp
 * @version v1.0
 * @since v1.0
 */
@Component
public class ScriptHelper {
    private static final Logger LOG = LoggerFactory.getLogger(ScriptHelper.class);
    @Resource
    private CompanyModel companyModel;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private PaymentModel paymentModel;
    @Resource
    private PaymentSheetModel paymentSheetModel;
    @Resource
    private PayPlanSheetModel payPlanSheetModel;
    @Resource
    private FundAccountFlowModel fundAccountFlowModel;
    @Resource
    private SaasUpdateHelp saasUpdateHelp;
    @Resource
    private FundHelp fundHelp;
    @Resource
    private PaymentSheetService paymentSheetService;
    @Resource
    private CustomerStatementModel customerStatementModel;
    @Resource
    private CustomerStatementService customerStatementService;
    @Resource
    private SaasCustomerStatementHelper saasCustomerStatementHelper;

    /**
     * 获取未删除的公司数量
     *
     * @return java.lang.Integer
     * @author 徐俊杰
     * @date 2019/12/19 17:31
     * @since v1.0
     * @update 2020/3/27 by zcp 从FundScriptController内移入
     */
    public Integer getCompanyCount() {
        //获取未删除的公司数量
        Map<String, Object> companyParam = new HashMap<>(BasicConstant.TWO);
        companyParam.put("del", BasicConstant.ZERO);
        return companyModel.getEntitysCount(companyParam);
    }

    /**
     * 批量获取一批公司的某种业务类型的表单解释（注：一次性查询多个公司的数据）
     * @param businessType 业务类型
     * @param corpidSet 公司id数组
     * @return java.util.List<com.xbongbong.paas.domain.entity.PaasFormExplainEntity>
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    public List<PaasFormExplainEntity> findExplainByCorpidSet(Integer businessType, Collection<String> corpidSet) throws XbbException {
        Map<String, Object> formParam = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        formParam.put("corpidIn", corpidSet);
        formParam.put("del", BasicConstant.ZERO);
        formParam.put("saasMark", SaasMarkEnum.SAAS.getCode());
        formParam.put("businessType", businessType);
        return paasFormExplainModel.list(formParam);
    }

    /**
     * 处理回/付款单data，并放入集合（兼容普通回款单和审批中回款单字段的刷新）
     * 获取关联客户id、关联合同id（入审批的数据格式可能为数组格式：[{"id":1, "name":""}]
     * @param idSet 回/付款计划等id集合
     * @param data 数据data
     * @param linkAttr 关联attr
     * @param linkHideAttr 关联业务隐藏
     * @return java.util.Set<java.lang.Long>
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    public Set<Long> getAssociatedConditions4Data(Set<Long> idSet, JSONObject data, String linkAttr, String linkHideAttr) {
        String linkIdStr = data.getString(linkAttr);
        Long linkId = -1L;
        try {
            linkId = Long.parseLong(linkIdStr);
        } catch (NumberFormatException e) {
            LOG.error("scriptHelper：数字类型转换错误linkIdStr："+linkIdStr,e);
            JSONArray jsonArray = JSONArray.parseArray(linkIdStr);
            if (jsonArray != null && jsonArray.size() > 0) {
                Object object = jsonArray.get(0);
                if (object instanceof Long) {
                    linkId = jsonArray.getLong(0);
                } else if (object instanceof String) {
                    linkId = StringUtil.StringToLong(jsonArray.getString(0));
                } else if (object instanceof JSONObject) {
                    JSONObject jsonObject = jsonArray.getJSONObject(0);
                    linkId = jsonObject.getLong(StringConstant.SAAS_LINK_BUSINESS_ID);
                    data.put(linkAttr, linkId);
                    data.put(linkHideAttr, jsonObject.getString(StringConstant.SAAS_LINK_BUSINESS_NAME));
                }
            }
        }
        idSet.add(linkId);
        return idSet;
    }

    @Async(value = "addUpdateDelThreadPool")
    public void handleFundLogAsync(String corpid, FundAccountExplainsDTO fundAccountExplainsDTO, Long accountId) throws XbbException {
        Map<String, Object> sheetParams = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        sheetParams.put(StringConstant.CORPID, corpid);
        sheetParams.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
        sheetParams.put(BasicConstant.ALONE_IN, SheetAloneEnum.getNotChildCodeList());
        if (Objects.equals(fundAccountExplainsDTO.getGreaterOrLess(), 0)) {
            sheetParams.put("addTimeEnd", fundAccountExplainsDTO.getAddTime());
            //前置标识  处理付款单无类型text_10字段前置部分数据
            sheetParams.put("isPreposition", "1");
        }else if (Objects.equals(fundAccountExplainsDTO.getGreaterOrLess(), 1)) {
            sheetParams.put("addTimeStart", fundAccountExplainsDTO.getAddTime());
        } else {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001.getCode(), "大于等于或小于标识错误");
        }
//                long startTestTime = System.currentTimeMillis();
        List<PaymentSheetEntityExt> paymentSheetEntityExts = paymentSheetModel.getPaymentSheetByCorpidScript(sheetParams);
        List<PayPlanSheetEntityExt> paySheetEntityExts = payPlanSheetModel.getPaySheetByCorpidScript(sheetParams);
//                LOG.warn("-----测试查询付、回款单耗时----------公司ID为："+ corpid + "：" + "   付、回款单总数为：" + (paymentSheetEntityExts.size() + paySheetEntityExts.size()) +
//                        "   耗时：" + (System.currentTimeMillis() - startTestTime));
        if (paySheetEntityExts.size() != 0 || paymentSheetEntityExts.size() != 0) {
            PaasFormExplainEntity paymentSheetExplain = paasFormExplainModel.getByBusinessType(XbbRefTypeEnum.PAYMENT_SHEET.getCode(), corpid);
            Map<String, FieldAttrEntity> paymentSheetExplainMap = paymentSheetExplain == null ? null : ExplainUtil.getExplainMap(paymentSheetExplain.getExplains(), PaymentSheetEnum.PAYMENT_TIME.getAttr());
            PaasFormExplainEntity paySheetExplain = paasFormExplainModel.getByBusinessType(XbbRefTypeEnum.PAY_SHEET.getCode(), corpid);
            Map<String, FieldAttrEntity> paySheetExplainMap = paySheetExplain == null ? null : ExplainUtil.getExplainMap(paySheetExplain.getExplains(), PaySheetEnum.REAL_PAY_TIME.getAttr());
            List<FundAccountFlowEntity> fundAccountFlowEntities = new ArrayList<>();
            paymentSheetEntityExts.forEach(e -> {
                JSONObject data = e.getData();
                Long addTime = e.getAddTime();
                String creatorId = e.getCreatorId();
                Long sheetTime = fundHelp.getSheetTime4FundFlow(data, PaymentSheetEnum.PAYMENT_TIME.getAttr(), paymentSheetExplainMap, addTime);
                Double sheetAmount;
                try {
                    sheetAmount = FastJsonHelper.getDoubleOrDefaultFromFormData(data, PaymentSheetEnum.AMOUNT.getAttr(), 0D);
                } catch (XbbException e1) {
                    LOG.error("---------------初始化流水信息读取金额报错，异常公司ID：" + corpid + " ,异常回款单ID---------------：" + e.getId());
                    sheetAmount = 0D;
                }
                FundAccountFlowEntity fundAccountFlowEntity = new FundAccountFlowEntity(corpid, addTime, addTime, creatorId, FundAccountFlowTypeEnum.PAYMENT_SHEET.getCode(), e.getId(), accountId, sheetTime, sheetAmount);
                fundAccountFlowEntities.add(fundAccountFlowEntity);
            });
            paySheetEntityExts.forEach(e -> {
                JSONObject data = e.getData();
                Long addTime = e.getAddTime();
                String creatorId = e.getCreatorId();
                Long sheetTime = fundHelp.getSheetTime4FundFlow(data, PaySheetEnum.REAL_PAY_TIME.getAttr(), paySheetExplainMap, addTime);
                Double sheetAmount;
                try {
                    sheetAmount = FastJsonHelper.getDoubleOrDefaultFromFormData(data, PaySheetEnum.AMOUNT.getAttr(), 0D);
                    //付款单金额取反
                    sheetAmount = Arith.mul(sheetAmount, -1);
                } catch (XbbException e1) {
                    LOG.error("---------------初始化流水信息读取金额报错，异常公司ID：" + corpid + " ,异常付款单ID---------------：" + e.getId());
                    sheetAmount = 0D;
                }
                FundAccountFlowEntity fundAccountFlowEntity = new FundAccountFlowEntity(corpid, addTime, addTime, creatorId, FundAccountFlowTypeEnum.PAY_SHEET.getCode(), e.getId(), accountId, sheetTime, sheetAmount);
                fundAccountFlowEntities.add(fundAccountFlowEntity);
            });
            int count = fundAccountFlowEntities.size();
            int start = 0;
            int pageNum = 500;
            if (count > 0) {
                for (; start <= count; start = start + pageNum) {
                    // 分组数据的endIndex
                    int endIndex = (start + pageNum) <= count ? start + pageNum : count;
                    List<FundAccountFlowEntity> flowEntityList = fundAccountFlowEntities.subList(start, endIndex);
                    if (flowEntityList.size() > 0) {
                        try {
                            fundAccountFlowModel.insertBatch(flowEntityList);
                        } catch (XbbException e) {
                            StringBuilder str = new StringBuilder();
                            flowEntityList.forEach(flowEntity -> str.append(flowEntity.getAccountId()).append(","));
                            LOG.error("---------------初始化流水信息报错，异常公司ID：" + corpid + " ,异常账户ID---------------：" + str.toString());
                        }
                    }
                    LOG.warn("---------------处理进度---------------：" + endIndex + "/" + count);
                }
            }
        }
    }

    @Async(value = "addUpdateDelThreadPool")
    public void handleFundAccountInPaymentAsync(String corpid, FundAccountExplainsDTO fundAccountExplainsDTO, Long accountId) throws XbbException{
        Map<String, Object> sheetParams = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        sheetParams.put(StringConstant.CORPID, corpid);
        sheetParams.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
        if (Objects.equals(fundAccountExplainsDTO.getGreaterOrLess(), 0)) {
            sheetParams.put("addTimeEnd", fundAccountExplainsDTO.getAddTime());
        } else if (Objects.equals(fundAccountExplainsDTO.getGreaterOrLess(), 1)) {
            sheetParams.put("addTimeStart", fundAccountExplainsDTO.getAddTime());
        } else {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001.getCode(), "大于等于或小于标识错误");
        }
        sheetParams.put(BasicConstant.ALONE_IN, SheetAloneEnum.getNotChildCodeList());
//                long startTestTime = System.currentTimeMillis();
        List<PaymentSheetEntityExt> paymentSheetEntityExts = paymentSheetModel.getPaymentSheetByCorpidScript(sheetParams);
//                LOG.warn("-----测试查询回款单耗时----------公司ID为："+ corpid + "：" + "   回款单总数为：" + paymentSheetEntityExts.size() + "   耗时：" + (System.currentTimeMillis() - startTestTime));
        List<PaymentSheetUpdateDTO> paymentSheetUpdateDTOList = new ArrayList<>();
        for (PaymentSheetEntityExt entityExt : paymentSheetEntityExts) {
            PaymentSheetUpdateDTO paymentSheetUpdateDTO = new PaymentSheetUpdateDTO();
            paymentSheetUpdateDTO.setId(entityExt.getId());
            JSONObject oldData = entityExt.getData();
            if (oldData.get(PaymentSheetEnum.FUND_ACCOUNT.getAttr()) != null) {
                continue;
            }
            JSONObject data = new JSONObject();
            data.put(PaymentSheetEnum.FUND_ACCOUNT.getAttr(), accountId.toString());
            paymentSheetUpdateDTO.setData(data);
            paymentSheetUpdateDTOList.add(paymentSheetUpdateDTO);
        }
        int count = paymentSheetUpdateDTOList.size();
        int start = 0;
        int pageNum = 500;
        if (count > 0) {
            for (; start <= count; start = start + pageNum) {
                // 分组数据的endIndex
                int endIndex = (start + pageNum) <= count ? start + pageNum : count;
                List<PaymentSheetUpdateDTO> updateList = paymentSheetUpdateDTOList.subList(start, endIndex);
                PaymentSheetUpdateBatchDTO paymentSheetUpdateBatchDTO = new PaymentSheetUpdateBatchDTO();
                paymentSheetUpdateBatchDTO.setCorpid(corpid);
                paymentSheetUpdateBatchDTO.setPaymentSheetUpdateList(updateList);
                try {
                    paymentSheetService.updateAttrBatch(paymentSheetUpdateBatchDTO, false);
                } catch (XbbException e) {
                    LOG.error("---------------初始化回款单中账户ID报错，异常公司ID：" + corpid);
                }
                LOG.warn("---------------处理进度---------------公司ID为："+ corpid +"：" + endIndex + "/" + count);
            }
        }
    }

    /**
     * 生成回款单的对账记录（按公司生成--因为涉及读取explain，计算对账日期等）
     * initCustomerStatementDTO内参数已校验
     * @param initCustomerStatementDTO
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    @Async(value = "addUpdateDelThreadPool")
    public void handleCustomerStatementAsync4Sheet(InitCustomerStatementDTO initCustomerStatementDTO) throws XbbException {
        Integer initFlag = initCustomerStatementDTO.getInitFlag();
        Long timestamp = initCustomerStatementDTO.getTimestamp();
        List<String> corpidList = initCustomerStatementDTO.getCorpidList();
        List<Long> customerIdList = initCustomerStatementDTO.getCustomerIdList();
        Map<String, Object> paramSheet = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //是否要直接插入对账记录（如果为false，则需要排重，查询已有数据，对其更新）
        boolean insertFlag = false;
        if (Objects.equals(initFlag, BasicConstant.ONE)) {
            //不需要排重，直接插入
            insertFlag = true;
            //1 代表前置执行脚本时的初始化，直接插入数据，【必须传timestamp】限制查询的结束时间
            //限制del、限制查询的结束时间
            paramSheet.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
            paramSheet.put("updateTimeEnd", timestamp);
        }else if (Objects.equals(initFlag, BasicConstant.TWO)) {
            //2 代表增量处理，【必须传timestamp】限制查询的开始时间（只处理增量部分），且不能限制del条件（把del的状态变更也要处理到statement表），需与statement表数据匹配是插入还是更新（只更改关联名称、del状态）
            //【不限制del（可能单据删了但对账记录未删，所以要处理。但是如果回款单和对应对账流水都为删除，则无需更新）】，但限制查询的开始时间
            paramSheet.put("updateTimeStart", timestamp);
        } else {
            //【不限制del】，视情况限制客户id
            if (CollectionUtils.isNotEmpty(customerIdList)) {
                //只查某个客户的回款单
                paramSheet.put("customerIdIn", customerIdList);
            }
        }
        Integer businessType = XbbRefTypeEnum.PAYMENT_SHEET.getCode();
        Map<String, Object> paramStatement = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //查对账单记录必须要限制businessType、公司id、statementId，不限制del（可能存在单据记录还正常，但对账记录被删除，需要恢复）
        paramStatement.put("businessType", businessType);
        int idx = 0;
        int totalCorp = corpidList.size();
        //只处理审批通过和不需审批的
        paramSheet.put("flowStatusIn", FlowStatusEnum.getStatisticType());
        //生成对账单按照最小维度生成
        paramSheet.put(BasicConstant.ALONE_IN, SheetAloneEnum.getNotParentCodeList());
        paramSheet.put("orderByStr", "id asc");
        Integer start = 0;
        //每次查询1000条
        Integer pageNum = 1000;
        paramSheet.put("start", start);
        paramSheet.put("pageNum", pageNum);
        Integer distributorMark = DistributorMarkEnum.OTHER.getCode();
        for (String corpid : corpidList) {
            paramStatement.put("corpid", corpid);
            paramSheet.put("corpid",corpid);
            PaasFormExplainEntity paymentSheetExplain = paasFormExplainModel.getByBusinessType4Distributor(businessType, corpid, distributorMark);
            Map<String, FieldAttrEntity> explainMap = paymentSheetExplain == null ? null : ExplainUtil.getExplainMap(paymentSheetExplain.getExplains(), PaymentSheetEnum.PAYMENT_TIME.getAttr());
            Long idGte = 0L;
            while (true) {
                try {
                    paramSheet.put("idGte", idGte);
                    List<PaymentSheetEntityExt> sheetList = paymentSheetModel.findEntitysWithoutSub(paramSheet);
                    if (CollectionUtils.isEmpty(sheetList)) {
                        break;
                    }
                    idGte = sheetList.get(sheetList.size() - 1).getId();
                    if (insertFlag) {
                        //直接插入，记录客户对账单
                        saasCustomerStatementHelper.save(businessType, explainMap, sheetList, distributorMark);
                    } else {
                        //先查询该批回款单对应的对账记录
                        Set<String> corpidSet = new HashSet<>();
                        Set<Long> sheetIdSet = new HashSet<>();
                        for (PaymentSheetEntityExt entityExt : sheetList) {
                            corpidSet.add(entityExt.getCorpid());
                            sheetIdSet.add(entityExt.getId());
                        }
                        paramStatement.put("sysLong2In", sheetIdSet);
                        List<CustomerStatementEntity> statementList = customerStatementModel.findEntitys(paramStatement);
                        /**
                         该批回款单sheetList有对应对账单记录，则需要做如下处理：
                         （1）把有对账记录的直接从sheetList内移除（因为这类回款单肯定不需再生成对账单-要么不处理，要么更新其对账记录）
                         （2）需要更新的对账记录塞入updateList，不需要更新的，则不做任何处理
                         （3）剩余其他的都做插入对账单记录处理（无对账记录的直接全部做插入对账单记录处理）
                         */
                        if (CollectionUtils.isNotEmpty(statementList)) {
                            //key为statementId（即回款单id，按公司执行，key没必要加上corpid）  value为实体CustomerStatementEntity
                            //Map<Long, CustomerStatementEntity> statementMap = statementList.stream().collect(Collectors.toMap(s -> s.getData().getLong(CustomerStatementEnum.STATEMENT_ID.getAttr()), Function.identity()));
                            Map<Long, CustomerStatementEntity> statementMap = statementList.stream().collect(Collectors.toMap(s -> s.getSysLong2(), Function.identity()));
                            //要更新的对账单list（可能有关联信息、del等被改动）
                            List<CustomerStatementEntity> updateList = new ArrayList<>();
                            Iterator<PaymentSheetEntityExt> iterator = sheetList.iterator();
                            while (iterator.hasNext()) {
                                PaymentSheetEntityExt sheetEntityExt = iterator.next();
                                CustomerStatementEntity statementEntity = statementMap.get(sheetEntityExt.getId());
                                Integer del = sheetEntityExt.getDel();
                                if (Objects.nonNull(statementEntity)) {
                                    Integer delStatement = statementEntity.getDel();
                                    //都为删除，不用处理
                                    if (Objects.equals(del, DelEnum.DELETE.getDel()) && Objects.equals(delStatement, del)) {
                                        iterator.remove();
                                        continue;
                                    }
                                    JSONObject statementData = statementEntity.getData();
                                    if (statementData == null) {
                                        statementData = new JSONObject();
                                    }
                                    //该回款单已经有其对账单记录，则不用生成对账单记录
                                    iterator.remove();
                                    JSONObject data = JsonHelperUtil.getJSONObject(sheetEntityExt.getData());
                                    /**
                                     把增量内的回款单核心信息置入对账单实体，进行更新
                                     当前只更新(判断是否相等，不相等才更新)：del、statementNo、customerName、contractNo、updateTime
                                     其他字段本身原单据也不能更改
                                     */
                                    boolean updateFlag = false;
                                    if (!Objects.equals(delStatement, del)) {
                                        updateFlag = true;
                                        statementEntity.setDel(del);
                                    }
                                    String serialNo = sheetEntityExt.getSerialNo();
                                    if (Objects.nonNull(serialNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.STATEMENT_NO.getAttr()), serialNo)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.STATEMENT_NO.getAttr(), serialNo);
                                    }
                                    String customerName = data.getString(PaymentSheetEnum.CUSTOMER_NAME_LINK_TEXT.getAttr());
                                    if (Objects.nonNull(customerName) && !Objects.equals(statementData.getString(CustomerStatementEnum.CUSTOMER_NAME.getAttr()), customerName)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CUSTOMER_NAME.getAttr(), customerName);
                                    }
                                    JSONArray contractNo = data.getJSONArray(PaymentSheetEnum.CONTRACT_LINK_TEXT.getAttr());
                                    if (CollectionUtils.isNotEmpty(contractNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.CONTRACT_NO.getAttr()),  contractNo.get(BasicConstant.ZERO))) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CONTRACT_NO.getAttr(), contractNo.get(BasicConstant.ZERO));
                                    }
                                    //回款金额
                                    Double amount = data.getDoubleValue(PaymentSheetEnum.AMOUNT.getAttr());
                                    if (!Objects.equals(statementData.getDoubleValue(CustomerStatementEnum.AMOUNT_RECEIVED.getAttr()), amount)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.AMOUNT_RECEIVED.getAttr(), amount);
                                    }
                                    //核销金额
                                    Double writeOffAmount = data.getDoubleValue(PaymentSheetEnum.WRITE_OFF_AMOUNT.getAttr());
                                    if (!Objects.equals(statementData.getDoubleValue(CustomerStatementEnum.WRITE_OFF_AMOUNT.getAttr()), writeOffAmount)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.WRITE_OFF_AMOUNT.getAttr(), writeOffAmount);
                                    }
                                    if (updateFlag) {
                                        statementEntity.setData(statementData);
                                        statementEntity.setUpdateTime(sheetEntityExt.getUpdateTime());
                                        updateList.add(statementEntity);
                                    }
                                } else if (Objects.equals(del, DelEnum.DELETE.getDel())) {
                                    //增量时间内新增的数据，是删除状态，无需生成对账单
                                    iterator.remove();
                                }
                            }
                            if (updateList.size() > 0) {
                                customerStatementModel.updateBatchAbnormal(updateList, corpid);
                            }
                        }
                        if (sheetList.size() > 0) {
                            //移除要更新其对应对账记录的单据之外，剩余的回款单，直接生成其客户对账单记录
                            saasCustomerStatementHelper.save(businessType, explainMap, sheetList, distributorMark);
                        }
                    }
                } catch (Exception e) {
                    LOG.error("公司（corpid=" + corpid + "）的sheet对账记录生成失败 initFlag(1 init, 2 update, 3 repair):" + initFlag, e);
                }
            }
            LOG.warn("回款单对账记录 公司ID：{} end---------------（所有公司）总处理进度---------------{}/{}", corpid, ++idx, totalCorp);
        }
    }
    /**
     * 生成回款单的对账记录（按公司生成--因为涉及读取explain，计算对账日期等）
     * initCustomerStatementDTO内参数已校验
     * @param initCustomerStatementDTO
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    @Async(value = "addUpdateDelThreadPool")
    public void handleCustomerStatementAsyncBatch4Sheet(InitCustomerStatementDTO initCustomerStatementDTO) throws XbbException {
        Integer initFlag = initCustomerStatementDTO.getInitFlag();
        Long timestamp = initCustomerStatementDTO.getTimestamp();
        String paramCorpid = initCustomerStatementDTO.getParamCorpid();
        Map<String, Object> paramSheet = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //是否要直接插入对账记录（false 初始化则不用排重；true 增量或修正，需要查询对账记录与回款单比较，确认是否更新回款单。）
        boolean repeatFlag = true;
        if (Objects.equals(initFlag, BasicConstant.ONE)) {
            //不需要排重，直接插入
            repeatFlag = false;
            //1 代表前置执行脚本时的初始化，直接插入数据，【必须传timestamp】限制查询的结束时间
            //限制del、限制查询的结束时间
            paramSheet.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
            paramSheet.put("updateTimeEnd", timestamp);
        }else if (Objects.equals(initFlag, BasicConstant.TWO)) {
            //2 代表增量处理，【必须传timestamp】限制查询的开始时间（只处理增量部分），且不能限制del条件（把del的状态变更也要处理到statement表），需与statement表数据匹配是插入还是更新（只更改关联名称、del状态）
            //【不限制del（可能单据删了但对账记录未删，所以要处理。但是如果回款单和对应对账流水都为删除，则无需更新）】，但限制查询的开始时间
            paramSheet.put("updateTimeStart", timestamp);
        } else {
            //【不限制del】，视情况限制客户id
            List<Long> customerIdList = initCustomerStatementDTO.getCustomerIdList();
            if (CollectionUtils.isNotEmpty(customerIdList)) {
                //只查某个客户的回款单
                paramSheet.put("customerIdIn", customerIdList);
            }
            paramSheet.put("corpid", paramCorpid);
        }
        //只处理审批通过和不需审批的
        paramSheet.put("flowStatusIn", FlowStatusEnum.getStatisticType());
        Long maxId = paymentSheetModel.getMaxId(paramSheet);
        //生成对账单按照最小维度生成
        paramSheet.put(BasicConstant.ALONE_IN, SheetAloneEnum.getNotParentCodeList());
        paramSheet.put("orderByStr", "id asc");
        Integer start = 0;
        //每次查询1000条
        Integer pageNum = 1000;
        paramSheet.put("start", start);
        paramSheet.put("pageNum", pageNum);
        Long idGte = 0L;
        Integer businessType = XbbRefTypeEnum.PAYMENT_SHEET.getCode();
        Map<String, Object> paramStatement = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //查对账单记录必须要限制businessType、公司id、statementId，不限制del（可能存在单据记录还正常，但对账记录被删除，需要恢复）
        paramStatement.put("businessType", businessType);
        Map<String, Object> paramExplain = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        paramExplain.put(ParameterConstant.DEL, DelEnum.NORMAL.getDel());
        //查出回款单、对账单的解释（需要用对账单的appId等）
        paramExplain.put("businessTypeIn", Arrays.asList(businessType, XbbRefTypeEnum.CUSTOMER_STATEMENT.getCode()));
        boolean dealWithEs = Objects.isNull(initCustomerStatementDTO.getDealWithEs()) || Objects.equals(BasicConstant.ONE, initCustomerStatementDTO.getDealWithEs());
        while (true) {
            try {
                //模拟滚动分页查询回款单
                paramSheet.put("idGte", idGte);
                List<PaymentSheetEntityExt> sheetList = paymentSheetModel.findEntitysWithoutSub(paramSheet);
                if (CollectionUtils.isEmpty(sheetList)) {
                    break;
                }
                idGte = sheetList.get(sheetList.size() - 1).getId();
                Set<String> corpidSet = new HashSet<>();
                Set<Long> sheetIdSet = new HashSet<>();
                for (PaymentSheetEntityExt entityExt : sheetList) {
                    corpidSet.add(entityExt.getCorpid());
                    sheetIdSet.add(entityExt.getId());
                }
                paramExplain.put("corpidIn", corpidSet);
                List<PaasFormExplainEntity> explainList = paasFormExplainModel.getNoHandleExplainsByFindEntitys(paramExplain);
                //回款单 key为corpid_formId；value为explainMap（只有时间字段，如回款时间、预计回款时间，因为插入对账单的对账日期需要用到）
                Map<String, Map<String, FieldAttrEntity>> payExplainMap = new HashMap<>(explainList.size());
                //回款单 key为corpid_formId；value为distributorMark
                Map<String, Integer> payDistributorMarkMap = new HashMap<>(explainList.size());
                //对账单 key为corpid_distributorMark；value为PaasFormExplainEntity（对账单记录需要存对账单的appId、menuId、formId）
                Map<String, PaasFormExplainEntity> statementExplainMap = new HashMap<>(explainList.size());
                for (PaasFormExplainEntity explainEntity : explainList) {
                    if (Objects.equals(businessType, explainEntity.getBusinessType())) {
                        String key = String.format(SymbolConstant.UNDERLINE_PLACE, explainEntity.getCorpid(), explainEntity.getFormId());
                        Map<String, FieldAttrEntity> explainMap = ExplainUtil.getExplainMap(explainEntity.getExplains(), PaymentSheetEnum.PAYMENT_TIME.getAttr());
                        payExplainMap.put(key, explainMap);
                        payDistributorMarkMap.put(key, explainEntity.getDistributorMark());
                    } else {
                        //对账单
                        String key = String.format(SymbolConstant.UNDERLINE_PLACE, explainEntity.getCorpid(), explainEntity.getDistributorMark());
                        statementExplainMap.put(key, explainEntity);
                    }
                }
                if (repeatFlag) {
                    /**
                     注：查重是需要对已经生成的对账记录进行比对，确认其是否要删除/更新。查重时，未限制回款单的del条件
                     该批回款单sheetList有对应对账单记录，则需要做如下处理：
                     （1）把有对账记录的直接从sheetList内移除（因为这类回款单肯定不需再生成对账单-要么不处理，要么更新其对账记录）
                     （2）需要更新的对账记录塞入updateList，不需要更新的，则不做任何处理
                     （3）剩余其他的都做插入对账单记录处理（无对账记录的直接全部做插入对账单记录处理）
                     */
                    //先查询该批回款单对应的对账记录
                    paramStatement.put("sysLong2In", sheetIdSet);
                    List<CustomerStatementEntity> statementList = customerStatementModel.findEntitys(paramStatement);
                    if (CollectionUtils.isNotEmpty(statementList)) {
                        //回款单id-回款单实体 (此时的sheetList是没有限制del状态的全list，不会为空，如果为空上面已经break)
                        Map<Long, PaymentSheetEntityExt> sheetMap = sheetList.stream().collect(Collectors.toMap(s -> s.getId(), Function.identity()));
                        //要更新的对账单list（可能有关联信息、del等被改动）
                        List<CustomerStatementEntity> updateList = new ArrayList<>();
                        Iterator<CustomerStatementEntity> iterator = statementList.iterator();
                        long now = DateTimeUtil.getInt();
                        while (iterator.hasNext()) {
                            CustomerStatementEntity statementEntity = iterator.next();
                            Long sheetId = statementEntity.getSysLong2();
                            //Long sheetId = statementEntity.getData().getLong(CustomerStatementEnum.STATEMENT_ID.getAttr());
                            PaymentSheetEntityExt sheetEntityExt = sheetMap.get(sheetId);
                            //增量处理时回款单有更新，且有其对应的对账单记录，则判断是否需要更新对账记录；或者对账记录有重复，则移除多余的对账记录
                            if (Objects.nonNull(sheetEntityExt)) {
                                Integer del = sheetEntityExt.getDel();
                                Integer delStatement = statementEntity.getDel();
                                //都为删除，不用处理，也不能新增
                                if (Objects.equals(del, DelEnum.DELETE.getDel()) && Objects.equals(delStatement, del)) {
                                    iterator.remove();
                                    sheetMap.remove(sheetId);
                                    sheetList.remove(sheetEntityExt);
                                    continue;
                                }
                                JSONObject statementData = statementEntity.getData();
                                if (statementData == null) {
                                    statementData = new JSONObject();
                                }
                                JSONObject data = JsonHelperUtil.getJSONObject(sheetEntityExt.getData());
                                /**
                                 把增量内的回款单核心信息置入对账单实体，进行更新
                                 当前只更新(判断是否相等，不相等才更新)：del、statementNo、customerName、contractNo、updateTime
                                 其他字段本身原单据也不能更改
                                 */
                                boolean updateFlag = false;
                                if (!Objects.equals(delStatement, del)) {
                                    updateFlag = true;
                                    statementEntity.setDel(del);
                                }
                                String serialNo = sheetEntityExt.getSerialNo();
                                if (Objects.nonNull(serialNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.STATEMENT_NO.getAttr()), serialNo)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.STATEMENT_NO.getAttr(), serialNo);
                                }
                                String customerName = data.getString(PaymentSheetEnum.CUSTOMER_NAME_LINK_TEXT.getAttr());
                                if (Objects.nonNull(customerName) && !Objects.equals(statementData.getString(CustomerStatementEnum.CUSTOMER_NAME.getAttr()), customerName)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CUSTOMER_NAME.getAttr(), customerName);
                                }
                                JSONArray contractNo = data.getJSONArray(PaymentSheetEnum.CONTRACT_LINK_TEXT.getAttr());
                                if (CollectionUtils.isNotEmpty(contractNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.CONTRACT_NO.getAttr()),  contractNo.get(BasicConstant.ZERO))) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CONTRACT_NO.getAttr(), contractNo.get(BasicConstant.ZERO));
                                }
                                //回款金额
                                Double amount = data.getDoubleValue(PaymentSheetEnum.AMOUNT.getAttr());
                                if (!Objects.equals(statementData.getDoubleValue(CustomerStatementEnum.AMOUNT_RECEIVED.getAttr()), amount)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.AMOUNT_RECEIVED.getAttr(), amount);
                                }
                                //核销金额
                                Double writeOffAmount = data.getDoubleValue(PaymentSheetEnum.WRITE_OFF_AMOUNT.getAttr());
                                if (!Objects.equals(statementData.getDoubleValue(CustomerStatementEnum.WRITE_OFF_AMOUNT.getAttr()), writeOffAmount)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.WRITE_OFF_AMOUNT.getAttr(), writeOffAmount);
                                }
                                if (updateFlag) {
                                    statementEntity.setData(statementData);
                                    statementEntity.setUpdateTime(sheetEntityExt.getUpdateTime());
                                    updateList.add(statementEntity);
                                }
                                //去除需要更新或不需要处理的数据，剩余可能要新增的数据
                                iterator.remove();
                                sheetMap.remove(sheetId);
                                sheetList.remove(sheetEntityExt);
                            } else {
                                //每条回款单对应一条对账记录，在if内处理后即已移除，则再循环到的重复对账记录没有对接回款单，这种重复的直接删除即可
                                statementEntity.setDel(1);
                                statementEntity.setUpdateTime(now);
                                updateList.add(statementEntity);
                                //增量时间内新增的数据，是删除状态，无需生成对账单
                                iterator.remove();
                            }
                        }
                        //对账单内没有的回款单数据，则过滤本身就是删除的回款单，不用生成已删除单据的对账记录
                        for (Map.Entry<Long, PaymentSheetEntityExt> entry : sheetMap.entrySet()) {
                            PaymentSheetEntityExt entityExt = entry.getValue();
                            if (Objects.equals(entityExt.getDel(), DelEnum.DELETE.getDel())) {
                                sheetList.remove(entityExt);
                            }
                        }
                        if (updateList.size() > 0) {
                            if (Objects.equals(initFlag, BasicConstant.TWO)) {
                                //处理增量时未按照公司
                                customerStatementModel.updateBatchAbnormal(updateList, null);
                            } else {
                                customerStatementModel.updateBatchAbnormal(updateList, paramCorpid);
                            }
                        }
                    }
                }
                if (sheetList.size() > 0) {
                    //初始化或排重后移除要更新其对应对账记录的单据之外，剩余的回款单，直接生成其客户对账单记录
                    customerStatementService.insertBatch4Script(dealWithEs, businessType, payExplainMap, payDistributorMarkMap, statementExplainMap, sheetList);
                }
            } catch (Exception e) {
                LOG.error("sheet生成对账记录失败，处理到idGte（idGte=" + idGte + "，步长pageNum=" + pageNum + "） initFlag(1 init, 2 update, 3 repair):" + initFlag + "   " + e, e);
            }
            LOG.warn("sheet -- 回款单生成对账记录处理进度---------------(当前已处理ID/最大sheetId):{}/{}", idGte, maxId);
        }
    }

    /**
     * 生成应收款的对账记录（按公司生成--因为涉及读取explain，计算对账日期等）
     * initCustomerStatementDTO内参数已校验
     * @param initCustomerStatementDTO
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    @Async(value = "addUpdateDelThreadPool")
    public void handleCustomerStatementAsync4Payment(InitCustomerStatementDTO initCustomerStatementDTO) throws XbbException {
        Integer initFlag = initCustomerStatementDTO.getInitFlag();
        Long timestamp = initCustomerStatementDTO.getTimestamp();
        List<String> corpidList = initCustomerStatementDTO.getCorpidList();
        List<Long> customerIdList = initCustomerStatementDTO.getCustomerIdList();
        Map<String, Object> paramPayment = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //是否要直接插入对账记录（如果为false，则需要排重，查询已有数据，对其更新）
        boolean insertFlag = false;
        if (Objects.equals(initFlag, BasicConstant.ONE)) {
            //不需要排重，直接插入
            insertFlag = true;
            //1 代表前置执行脚本时的初始化，直接插入数据，【必须传timestamp】限制查询的结束时间
            //限制del、限制查询的结束时间
            paramPayment.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
            paramPayment.put("updateTimeEnd", timestamp);
        }else if (Objects.equals(initFlag, BasicConstant.TWO)) {
            //2 代表增量处理，【必须传timestamp】限制查询的开始时间（只处理增量部分），且不能限制del条件（把del的状态变更也要处理到statement表），需与statement表数据匹配是插入还是更新（只更改关联名称、del状态）
            //【不限制del】，但限制查询的开始时间
            paramPayment.put("updateTimeStart", timestamp);
        } else {
            //【不限制del】，【不限制时间】，视情况限制客户id
            if (CollectionUtils.isNotEmpty(customerIdList)) {
                //只查某个客户的应收款
                paramPayment.put("customerIdIn", customerIdList);
            }
        }
        Integer businessType = XbbRefTypeEnum.PAYMENT.getCode();
        Map<String, Object> paramStatement = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //查对账单记录必须要限制businessType、公司id、statementId，不限制del（可能存在单据记录还正常，但对账记录被删除，需要恢复）
        paramStatement.put("businessType", businessType);
        int idx = 0;
        int totalCorp = corpidList.size();
        //应收款无审批
        paramPayment.put("orderByStr", "id asc");
        Integer distributorMark = DistributorMarkEnum.OTHER.getCode();
        Integer start = 0;
        //每次查询1000条
        Integer pageNum = 1000;
        paramPayment.put("start", start);
        paramPayment.put("pageNum", pageNum);
        for (String corpid : corpidList) {
            paramStatement.put("corpid", corpid);
            Long idGte = 0L;
            paramPayment.put("corpid",corpid);
            PaasFormExplainEntity paymentExplain = paasFormExplainModel.getByBusinessType4Distributor(businessType, corpid, distributorMark);
            Map<String, FieldAttrEntity> explainMap = paymentExplain == null ? null : ExplainUtil.getExplainMap(paymentExplain.getExplains(), PaymentEnum.ESTIMATE_TIME.getAttr());
            while (true) {
                paramPayment.put("idGte", idGte);
                List<PaymentEntityExt> paymentList = paymentModel.findEntitysWithoutSub(paramPayment);
                if (CollectionUtils.isEmpty(paymentList)) {
                    break;
                }
                idGte = paymentList.get(paymentList.size() - 1).getId();
                try {
                    if (insertFlag) {
                        //直接插入，记录客户对账单
                        saasCustomerStatementHelper.save(businessType, explainMap, paymentList, distributorMark);
                    } else {
                        //先查询该批应收款对应的对账记录
                        Set<String> corpidSet = new HashSet<>();
                        Set<Long> paymentIdSet = new HashSet<>();
                        for (PaymentEntityExt entityExt : paymentList) {
                            corpidSet.add(entityExt.getCorpid());
                            paymentIdSet.add(entityExt.getId());
                        }
                        paramStatement.put("sysLong2In", paymentIdSet);
                        List<CustomerStatementEntity> statementList = customerStatementModel.findEntitys(paramStatement);
                        /**
                         该批应收款sheetList有对应对账单记录，则需要做如下处理：
                         （1）把有对账记录的直接从sheetList内移除（因为这类应收款肯定不需再生成对账单-要么不处理，要么更新其对账记录）
                         （2）需要更新的对账记录塞入updateList，不需要更新的，则不做任何处理
                         （3）剩余其他的都做插入对账单记录处理（无对账记录的直接全部做插入对账单记录处理）
                         */
                        if (CollectionUtils.isNotEmpty(statementList)) {
                            //key为statementId（即应收款id，按公司执行，key没必要加上corpid）  value为实体CustomerStatementEntity
                            //Map<Long, CustomerStatementEntity> statementMap = statementList.stream().collect(Collectors.toMap(s -> s.getData().getLong(CustomerStatementEnum.STATEMENT_ID.getAttr()), Function.identity()));
                            Map<Long, CustomerStatementEntity> statementMap = statementList.stream().collect(Collectors.toMap(s -> s.getSysLong2(), Function.identity()));
                            //要更新的对账单list（可能有关联信息、del等被改动）
                            List<CustomerStatementEntity> updateList = new ArrayList<>();
                            Iterator<PaymentEntityExt> iterator = paymentList.iterator();
                            while (iterator.hasNext()) {
                                PaymentEntityExt paymentEntityExt = iterator.next();
                                CustomerStatementEntity statementEntity = statementMap.get(paymentEntityExt.getId());
                                Integer del = paymentEntityExt.getDel();
                                if (Objects.nonNull(statementEntity)) {
                                    Integer delStatement = statementEntity.getDel();
                                    //都为删除，不用处理
                                    if (Objects.equals(del, DelEnum.DELETE.getDel()) && Objects.equals(delStatement, del)) {
                                        iterator.remove();
                                        continue;
                                    }
                                    JSONObject statementData = statementEntity.getData();
                                    if (statementData == null) {
                                        statementData = new JSONObject();
                                    }
                                    //该应收款已经有其对账单记录，则不用生成对账单记录
                                    iterator.remove();
                                    JSONObject data = JsonHelperUtil.getJSONObject(paymentEntityExt.getData());
                                    /**
                                     把增量内的应收款核心信息置入对账单实体，进行更新
                                     当前只更新(判断是否相等，不相等才更新)：del、statementNo、customerName、contractNo、updateTime，checkingDate、customerId、contractId、amountReceivable
                                     注：受限于线上未收款状态的旧回款计划，关联信息、金额、时间都可以随意更改，所以该update方法只能同步更新对账日期、应收金额、关联客户等信息。后续资金3期上线后，需要舍弃该方法，或把这些本不应该被改动的信息去掉
                                     */
                                    boolean updateFlag = false;
                                    if (!Objects.equals(delStatement, del)) {
                                        updateFlag = true;
                                        statementEntity.setDel(del);
                                    }
                                    String serialNo = paymentEntityExt.getSerialNo();
                                    if (Objects.nonNull(serialNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.STATEMENT_NO.getAttr()), serialNo)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.STATEMENT_NO.getAttr(), serialNo);
                                    }
                                    String customerName = data.getString(PaymentEnum.CUSTOMER_LINK_TEXT.getAttr());
                                    if (Objects.nonNull(customerName) && !Objects.equals(statementData.getString(CustomerStatementEnum.CUSTOMER_NAME.getAttr()), customerName)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CUSTOMER_NAME.getAttr(), customerName);
                                    }
                                    String contractNo = data.getString(PaymentEnum.CONTRACT_LINK_TEXT.getAttr());
                                    if (Objects.nonNull(contractNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.CONTRACT_NO.getAttr()), contractNo)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CONTRACT_NO.getAttr(), contractNo);
                                    }
                                    Long checkingDate = data.getLong(PaymentEnum.ESTIMATE_TIME.getAttr());
                                    if (Objects.nonNull(checkingDate) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CHECKING_DATE.getAttr()), checkingDate)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CHECKING_DATE.getAttr(), checkingDate);
                                    }
                                    Long customerId = data.getLong(PaymentEnum.CUSTOMER.getAttr());
                                    if (Objects.nonNull(customerId) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CUSTOMER_ID.getAttr()), customerId)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CUSTOMER_ID.getAttr(), customerId);
                                    }
                                    Long contractId = data.getLong(PaymentEnum.CONTRACT.getAttr());
                                    if (Objects.nonNull(contractId) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CONTRACT_ID.getAttr()), contractId)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.CONTRACT_ID.getAttr(), contractId);
                                    }
                                    double amountReceivable = data.getDoubleValue(PaymentEnum.AMOUNT.getAttr());
                                    if (!Objects.equals(statementData.getDouble(CustomerStatementEnum.AMOUNT_RECEIVABLE.getAttr()), amountReceivable)) {
                                        updateFlag = true;
                                        statementData.put(CustomerStatementEnum.AMOUNT_RECEIVABLE.getAttr(), amountReceivable);
                                    }
                                    if (updateFlag) {
                                        statementEntity.setData(statementData);
                                        statementEntity.setUpdateTime(paymentEntityExt.getUpdateTime());
                                        updateList.add(statementEntity);
                                    }
                                } else if (Objects.equals(del, DelEnum.DELETE.getDel())) {
                                    //增量时间内新增的数据，是删除状态，无需生成对账单
                                    iterator.remove();
                                }
                            }
                            if (updateList.size() > 0) {
                                customerStatementModel.updateBatchAbnormal(updateList, corpid);
                            }
                        }
                        if (paymentList.size() > 0) {
                            //移除要更新其对应对账记录的单据之外，剩余的应收款，直接生成其客户对账单记录
                            saasCustomerStatementHelper.save(businessType, explainMap, paymentList, distributorMark);
                        }
                    }
                } catch (Exception e) {
                    LOG.error("公司（corpid=" + corpid + "）的payment对账记录生成失败 initFlag(1 init, 2 update, 3 repair):" + initFlag, e);
                }
            }
            LOG.warn("应收款对账记录 公司ID：{} end---------------（所有公司）总处理进度---------------{}/{}", corpid, ++idx, totalCorp);
        }
    }

    /**
     * 生成应收款的对账记录（按公司生成--因为涉及读取explain，计算对账日期等）
     * initCustomerStatementDTO内参数已校验
     * @param initCustomerStatementDTO
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    @Async(value = "addUpdateDelThreadPool")
    public void handleCustomerStatementAsyncBatch4Payment(InitCustomerStatementDTO initCustomerStatementDTO) throws XbbException {
        Integer initFlag = initCustomerStatementDTO.getInitFlag();
        Long timestamp = initCustomerStatementDTO.getTimestamp();
        String paramCorpid = initCustomerStatementDTO.getParamCorpid();
        Map<String, Object> paramPayment = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //是否要直接插入对账记录（false 初始化则不用排重；true 增量或修正，需要查询对账记录与回款单比较，确认是否更新回款单。）
        boolean repeatFlag = true;
        if (Objects.equals(initFlag, BasicConstant.ONE)) {
            //不需要排重，直接插入
            repeatFlag = false;
            //1 代表前置执行脚本时的初始化，直接插入数据，【必须传timestamp】限制查询的结束时间
            //限制del、限制查询的结束时间
            paramPayment.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
            paramPayment.put("updateTimeEnd", timestamp);
        }else if (Objects.equals(initFlag, BasicConstant.TWO)) {
            //2 代表增量处理，【必须传timestamp】限制查询的开始时间（只处理增量部分），且不能限制del条件（把del的状态变更也要处理到statement表），需与statement表数据匹配是插入还是更新（只更改关联名称、del状态）
            //【不限制del】，但限制查询的开始时间
            paramPayment.put("updateTimeStart", timestamp);
        } else {
            //【不限制del】，视情况限制客户id
            List<Long> customerIdList = initCustomerStatementDTO.getCustomerIdList();
            if (CollectionUtils.isNotEmpty(customerIdList)) {
                //只查某个客户的应收款
                paramPayment.put("customerIdIn", customerIdList);
            }
        }
        //只处理审批通过和不需审批的
        paramPayment.put("flowStatus", FlowStatusEnum.getStatisticType());
        Long maxId = paymentModel.getMaxId(paramPayment);
        //应收款无审批
        paramPayment.put("orderByStr", "id asc");
        Integer start = 0;
        //每次查询1000条
        Integer pageNum = 1000;
        paramPayment.put("start", start);
        paramPayment.put("pageNum", pageNum);
        Long idGte = 0L;
        Integer businessType = XbbRefTypeEnum.PAYMENT.getCode();
        Map<String, Object> paramStatement = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        //查对账单记录必须要限制businessType、公司id、statementId，不限制del（可能存在单据记录还正常，但对账记录被删除，需要恢复）
        paramStatement.put("businessType", businessType);
        Map<String, Object> paramExplain = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        paramExplain.put(ParameterConstant.DEL, DelEnum.NORMAL.getDel());
        //查出应收款、对账单的解释（需要用对账单的appId等）
        paramExplain.put("businessTypeIn", Arrays.asList(businessType, XbbRefTypeEnum.CUSTOMER_STATEMENT.getCode()));
        boolean dealWithEs = Objects.isNull(initCustomerStatementDTO.getDealWithEs()) || Objects.equals(BasicConstant.ONE, initCustomerStatementDTO.getDealWithEs());
        while (true) {
            paramPayment.put("idGte", idGte);
            List<PaymentEntityExt> paymentList = paymentModel.findEntitysWithoutSub(paramPayment);
            if (CollectionUtils.isEmpty(paymentList)) {
                break;
            }
            idGte = paymentList.get(paymentList.size() - 1).getId();
            Set<String> corpidSet = new HashSet<>();
            Set<Long> payIdSet = new HashSet<>();
            for (PaymentEntityExt entityExt : paymentList) {
                corpidSet.add(entityExt.getCorpid());
                payIdSet.add(entityExt.getId());
            }
            paramExplain.put("corpidIn", corpidSet);
            List<PaasFormExplainEntity> explainList = paasFormExplainModel.getNoHandleExplainsByFindEntitys(paramExplain);
            //回款单/应收款 key为corpid_formId；value为explainMap（只有时间字段，如回款时间、预计回款时间，因为插入对账单的对账日期需要用到）
            Map<String, Map<String, FieldAttrEntity>> payExplainMap = new HashMap<>(explainList.size());
            //回款单/应收款 key为corpid_formId；value为distributorMark
            Map<String, Integer> payDistributorMarkMap = new HashMap<>(explainList.size());
            //对账单 key为corpid_distributorMark；value为PaasFormExplainEntity（对账单记录需要存对账单的appId、menuId、formId）
            Map<String, PaasFormExplainEntity> statementExplainMap = new HashMap<>(explainList.size());
            for (PaasFormExplainEntity explainEntity : explainList) {
                if (Objects.equals(businessType, explainEntity.getBusinessType())) {
                    String key = String.format(SymbolConstant.UNDERLINE_PLACE, explainEntity.getCorpid(), explainEntity.getFormId());
                    Map<String, FieldAttrEntity> explainMap = ExplainUtil.getExplainMap(explainEntity.getExplains(), PaymentEnum.ESTIMATE_TIME.getAttr());
                    payExplainMap.put(key, explainMap);
                    payDistributorMarkMap.put(key, explainEntity.getDistributorMark());
                } else {
                    //对账单
                    String key = String.format(SymbolConstant.UNDERLINE_PLACE, explainEntity.getCorpid(), explainEntity.getDistributorMark());
                    statementExplainMap.put(key, explainEntity);
                }
            }
            try {
                if (repeatFlag) {
                    /**
                     注：查重是需要对已经生成的对账记录进行比对，确认其是否要删除/更新。查重时，未限制应收款的del条件
                     该批应收款sheetList有对应对账单记录，则需要做如下处理：
                     （1）把有对账记录的直接从sheetList内移除（因为这类应收款肯定不需再生成对账单-要么不处理，要么更新其对账记录）
                     （2）需要更新的对账记录塞入updateList，不需要更新的，则不做任何处理
                     （3）剩余其他的都做插入对账单记录处理（无对账记录的直接全部做插入对账单记录处理）
                     */
                    paramStatement.put("sysLong2In", payIdSet);
                    List<CustomerStatementEntity> statementList = customerStatementModel.findEntitys(paramStatement);
                    if (CollectionUtils.isNotEmpty(statementList)) {
                        //应收款id-应收款实体 (此时的paymentList是没有限制del状态的全list，不会为空，如果为空上面已经break)
                        Map<Long, PaymentEntityExt> sheetMap = paymentList.stream().collect(Collectors.toMap(s -> s.getId(), Function.identity()));
                        //要更新的对账单list（可能有关联信息、del等被改动）
                        List<CustomerStatementEntity> updateList = new ArrayList<>();
                        Iterator<CustomerStatementEntity> iterator = statementList.iterator();
                        long now = DateTimeUtil.getInt();
                        while (iterator.hasNext()) {
                            CustomerStatementEntity statementEntity = iterator.next();
                            //Long sheetId = statementEntity.getData().getLong(CustomerStatementEnum.STATEMENT_ID.getAttr());
                            Long sheetId = statementEntity.getSysLong2();
                            PaymentEntityExt paymentEntityExt = sheetMap.get(sheetId);
                            //增量处理时应收款有更新，且有其对应的对账单记录，则判断是否需要更新对账记录；或者对账记录有重复，则移除多余的对账记录
                            if (Objects.nonNull(paymentEntityExt)) {
                                Integer del = paymentEntityExt.getDel();
                                Integer delStatement = statementEntity.getDel();
                                //都为删除，不用处理
                                if (Objects.equals(del, DelEnum.DELETE.getDel()) && Objects.equals(delStatement, del)) {
                                    iterator.remove();
                                    sheetMap.remove(sheetId);
                                    paymentList.remove(paymentEntityExt);
                                    continue;
                                }
                                JSONObject statementData = statementEntity.getData();
                                if (statementData == null) {
                                    statementData = new JSONObject();
                                }
                                JSONObject data = JsonHelperUtil.getJSONObject(paymentEntityExt.getData());
                                /**
                                 把增量内的应收款核心信息置入对账单实体，进行更新
                                 当前只更新(判断是否相等，不相等才更新)：del、statementNo、customerName、contractNo、updateTime，checkingDate、customerId、contractId、amountReceivable
                                 注：受限于线上未收款状态的旧回款计划，关联信息、金额、时间都可以随意更改，所以该update方法只能同步更新对账日期、应收金额、关联客户等信息。后续资金3期上线后，需要舍弃该方法，或把这些本不应该被改动的信息去掉
                                 */
                                boolean updateFlag = false;
                                if (!Objects.equals(delStatement, del)) {
                                    updateFlag = true;
                                    statementEntity.setDel(del);
                                }
                                String serialNo = paymentEntityExt.getSerialNo();
                                if (Objects.nonNull(serialNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.STATEMENT_NO.getAttr()), serialNo)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.STATEMENT_NO.getAttr(), serialNo);
                                }
                                String customerName = data.getString(PaymentEnum.CUSTOMER_LINK_TEXT.getAttr());
                                if (Objects.nonNull(customerName) && !Objects.equals(statementData.getString(CustomerStatementEnum.CUSTOMER_NAME.getAttr()), customerName)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CUSTOMER_NAME.getAttr(), customerName);
                                }
                                String contractNo = data.getString(PaymentEnum.CONTRACT_LINK_TEXT.getAttr());
                                if (Objects.nonNull(contractNo) && !Objects.equals(statementData.getString(CustomerStatementEnum.CONTRACT_NO.getAttr()), contractNo)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CONTRACT_NO.getAttr(), contractNo);
                                }
                                Long checkingDate = data.getLong(PaymentEnum.ESTIMATE_TIME.getAttr());
                                if (Objects.nonNull(checkingDate) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CHECKING_DATE.getAttr()), checkingDate)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CHECKING_DATE.getAttr(), checkingDate);
                                }
                                Long customerId = data.getLong(PaymentEnum.CUSTOMER.getAttr());
                                if (Objects.nonNull(customerId) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CUSTOMER_ID.getAttr()), customerId)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CUSTOMER_ID.getAttr(), customerId);
                                }
                                Long contractId = data.getLong(PaymentEnum.CONTRACT.getAttr());
                                if (Objects.nonNull(contractId) && !Objects.equals(statementData.getLong(CustomerStatementEnum.CONTRACT_ID.getAttr()), contractId)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.CONTRACT_ID.getAttr(), contractId);
                                }
                                double amountReceivable = data.getDoubleValue(PaymentEnum.AMOUNT.getAttr());
                                if (!Objects.equals(statementData.getDouble(CustomerStatementEnum.AMOUNT_RECEIVABLE.getAttr()), amountReceivable)) {
                                    updateFlag = true;
                                    statementData.put(CustomerStatementEnum.AMOUNT_RECEIVABLE.getAttr(), amountReceivable);
                                }
                                if (updateFlag) {
                                    statementEntity.setData(statementData);
                                    statementEntity.setUpdateTime(paymentEntityExt.getUpdateTime());
                                    updateList.add(statementEntity);
                                }
                                //去除需要更新或不需要处理的数据，剩余可能要新增的数据
                                iterator.remove();
                                sheetMap.remove(sheetId);
                                paymentList.remove(paymentEntityExt);
                            } else {
                                //每条回款单对应一条对账记录，在if内处理后即已移除，则再循环到的重复对账记录没有对接回款单，这种重复的直接删除即可
                                statementEntity.setDel(1);
                                statementEntity.setUpdateTime(now);
                                updateList.add(statementEntity);
                                //增量时间内新增的数据，是删除状态，无需生成对账单
                                iterator.remove();
                            }
                        }
                        //对账单内没有的应收款数据，则过滤本身就是删除的应收款，不用生成已删除单据的对账记录
                        for (Map.Entry<Long, PaymentEntityExt> entry : sheetMap.entrySet()) {
                            PaymentEntityExt entityExt = entry.getValue();
                            if (Objects.equals(entityExt.getDel(), DelEnum.DELETE.getDel())) {
                                paymentList.remove(entityExt);
                            }
                        }
                        if (updateList.size() > 0) {
                            if (Objects.equals(initFlag, BasicConstant.TWO)) {
                                //处理增量时未按照公司
                                customerStatementModel.updateBatchAbnormal(updateList, null);
                            } else {
                                customerStatementModel.updateBatchAbnormal(updateList, paramCorpid);
                            }
                        }
                    }
                }
                if (paymentList.size() > 0) {
                    //初始化或排重后移除要更新其对应对账记录的单据之外，剩余的应收款，直接生成其客户对账单记录
                    customerStatementService.insertBatch4Script(dealWithEs, businessType, payExplainMap, payDistributorMarkMap, statementExplainMap, paymentList);
                }
            } catch (Exception e) {
                LOG.error("payment 生成对账记录失败，处理到idGte（idGte=" + idGte + "，步长pageNum=" + pageNum + "） initFlag(1 init, 2 update, 3 repair):" + initFlag + "   " + e, e);
            }
            LOG.warn("payment -- 应收款生成对账记录处理进度---------------(当前已处理ID/最大paymentId):{}/{}", idGte, maxId);
        }
    }
}
